using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Editor
{
    public class MapArea
    {
        public enum BlockType {Clear, Block, Cliff, Water, HighGround};

        #region Private Variables
        private BlockType selectedBlockType = BlockType.Block;
        private MapObject.ObjectType selectedMapObject = MapObject.ObjectType.SpawnPoint;

        private bool bProcessBusy = false;

        private SideMenu sideMenu;

        private Game game;

        private string strLevelName = "";

        private Texture2D mapTexture;
        private Texture2D mouseTexture;
        private Texture2D cellTexture;

        private MouseState lastMouseState;
        private MouseState currentMouseState;
        private KeyboardState lastKeyboardState;

        private Vector2 vPressPoint;
        private Vector2 vReleasePoint;

        private Vector2 vCameraPosition;

        private List<MapObject> lstMapObjects = new List<MapObject>();

        private int nNumberOfObjectTypes = Enum.GetValues(typeof(MapObject.ObjectType)).Length; //Number of different map objects.
        private Dictionary<MapObject.ObjectType, Texture2D> dicObjectTextures = new Dictionary<MapObject.ObjectType,Texture2D>();

        private int nNumberOfBlockTypes = Enum.GetValues(typeof(BlockType)).Length;

        private byte[,] cell;
        private int cellSize = 15;
        private int mapCellWidth;
        private int mapCellHeight;
        #endregion

        public bool bUseMapObject = false;

        #region Public Properties
        public BlockType CurrentBlockType
        {
            get
            {
                return this.selectedBlockType;
            }
            set
            {
                this.selectedBlockType = value;
            }
        }
        public MapObject.ObjectType CurrentObjectType
        {
            get
            {
                return this.selectedMapObject;
            }
            set
            {
                this.selectedMapObject = value;
            }
        }
        public SideMenu SideMenu
        {
            get
            {
                return this.sideMenu;
            }
        }
        #endregion

        public MapArea(Game game, Texture2D mapTexture, string levelName, Texture2D cellTexture, Texture2D mouseTexture, Dictionary<MapObject.ObjectType, Texture2D> objectTextures, Dictionary<Type, Texture2D> dicMenuItemTextures)
        {
            this.game = game;

            this.sideMenu = new SideMenu(this, dicMenuItemTextures);
            this.sideMenu.AddMenuItem(typeof(SaveMenuItem));
            this.sideMenu.AddMenuItem(typeof(LoadMenuItem));
            this.sideMenu.AddMenuItem(typeof(ClearMenuItem));
            this.sideMenu.AddMenuItem(typeof(SpawnMenuItem));
            this.sideMenu.AddMenuItem(typeof(MapObjectMenuItem));
            this.sideMenu.AddMenuItem(typeof(BlockMenuItem));
            this.sideMenu.AddMenuItem(typeof(CliffMenuItem));
            this.sideMenu.AddMenuItem(typeof(WaterMenuItem));
            this.sideMenu.AddMenuItem(typeof(HighGroundMenuItem));
            

            this.mapTexture = mapTexture;
            this.strLevelName = levelName;
            this.cellTexture = cellTexture;
            this.mouseTexture = mouseTexture;
            this.dicObjectTextures = objectTextures;

            //Calculate the number of cells for the map.
            mapCellWidth = mapTexture.Width / cellSize + 1;
            mapCellHeight = mapTexture.Height / cellSize + 1;
            cell = new byte[mapCellWidth, mapCellHeight];
            for (int x = 0; x < mapCellWidth; x++)
                for (int y = 0; y < mapCellHeight; y++)
                    cell[x, y] = (byte)BlockType.Clear;
        }

        public void Save()
        {
            this.bProcessBusy = true;

            XmlTextWriter mapWriter = null;
            mapWriter = new XmlTextWriter(this.strLevelName + ".xml", null);

            try
            {
                mapWriter.Formatting = Formatting.Indented;
                mapWriter.Indentation = 6;
                mapWriter.Namespaces = false;

                mapWriter.WriteStartDocument();

                mapWriter.WriteStartElement("", "Level", "");//<Level>

                mapWriter.WriteStartElement("", "MapImage", "");
                mapWriter.WriteString("Level3");
                mapWriter.WriteEndElement();

                mapWriter.WriteStartElement("", "CellSize", "");
                mapWriter.WriteString(this.cellSize.ToString());
                mapWriter.WriteEndElement();

                mapWriter.WriteStartElement("", "Grid", "");
                mapWriter.WriteString(this.gridToString());
                mapWriter.WriteEndElement();

                mapWriter.WriteStartElement("", "MapObjects", "");
                string strMapObjectString = "";
                foreach (MapObject mapobject in this.lstMapObjects)
                {
                    strMapObjectString += mapobject.GetString();
                }
                mapWriter.WriteString(strMapObjectString);
                mapWriter.WriteEndElement();

                mapWriter.WriteEndElement();//<\Level>
                mapWriter.WriteEndDocument();
                mapWriter.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception: {0}", e.ToString());
            }

            this.bProcessBusy = false;
        }

        public void Load()
        {
            this.bProcessBusy = true;

            XmlTextReader mapReader = null;
            mapReader = new XmlTextReader(strLevelName + ".xml");

            while (mapReader.Read())
            {
                if (mapReader.NodeType == XmlNodeType.Element)
                {
                    if (mapReader.LocalName.Equals("MapObjects"))
                    {
                        this.lstMapObjects.Clear();

                        //Parse string into individual objects
                        string[] mapObjectSplitString = mapReader.ReadString().Split(new char[] { '\n' });
                        foreach (string currentMapObject in mapObjectSplitString)
                        {
                            string[] InfoSplitString = currentMapObject.Split(new char[] { ' ' });
                            if (InfoSplitString.Length >= 3)
                            {
                                this.lstMapObjects.Add(new MapObject((MapObject.ObjectType)(Convert.ToInt32((InfoSplitString[0]))), new Vector2(Convert.ToInt32(InfoSplitString[1]), Convert.ToInt32(InfoSplitString[2]))));
                            }
                        }
                    }
                    if (mapReader.LocalName.Equals("Grid"))
                    {
                        //Get the grid string
                        String gridString = mapReader.ReadString();


                        //Converts the grid string into a matrix of integers.
                        int x = 0;
                        int y = -1;
                        for (int s = 0; s < gridString.Length; s++)
                        {
                            if (gridString[s] == ' ')
                            {
                                x++;
                            }
                            else if (gridString[s] == '\n')
                            {
                                x = 0;
                                y++;
                            }
                            else if (gridString[s] == ';')
                            {
                                break;
                            }
                            else
                            {
                                cell[x, y] = (byte)Int32.Parse(gridString[s] + "");
                            }
                        }

                    }//If: Grid
                }
            }//While: read
            mapReader.Close();

            this.bProcessBusy = false;
        }

        public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphicsDevice)
        {
            this.sideMenu.Draw(spriteBatch, graphicsDevice);

            spriteBatch.Begin();

            //Draw the background map
            spriteBatch.Draw(this.mapTexture, Vector2.Zero - vCameraPosition, Color.White);

            //Draws all the mapObjects
            #region Draw mapObjects
            foreach (MapObject mapobject in this.lstMapObjects)
            {
                spriteBatch.Draw(this.dicObjectTextures[mapobject.Type], mapobject.Position - this.vCameraPosition, Color.White);
            }
            #endregion

            //Draws the grid properties including colliding blocks
            #region Draw Grid Properties
            for (int x = 0; x < mapCellWidth; x++)
                for (int y = 0; y < mapCellHeight; y++)
                {
                    if (cell[x, y] > 0)
                    {
                        Vector2 cellPosition = new Vector2(x * cellSize, y * cellSize);

                        //Determine the objects color.
                        Color objColor = Color.White;
                        if (cell[x, y] == (byte)BlockType.Block) //Block
                            objColor = new Color(255, 0, 0, 100);
                        else if (cell[x, y] == (byte)BlockType.Cliff)     //Cliff
                            objColor = new Color(0, 255, 0, 100);
                        else if (cell[x, y] == (byte)BlockType.Water)     //Water
                            objColor = new Color(0, 0, 255, 100);
                        else if (cell[x, y] == (byte)BlockType.HighGround)   //High Ground
                            objColor = new Color(255, 255, 0, 100);
                        spriteBatch.Draw(cellTexture, cellPosition - vCameraPosition, objColor);
                    }
                }
            #endregion

            //Draws all the objects at the mouse.
            #region Draw Mouse Objects

            Vector2 objPosition = new Vector2();
            objPosition.X = currentMouseState.X - (currentMouseState.X % 15);
            objPosition.Y = currentMouseState.Y - (currentMouseState.Y % 15);
            if (this.bUseMapObject)
            {
                spriteBatch.Draw(this.dicObjectTextures[this.selectedMapObject], objPosition, Color.White);
            }
            else  //Else draw a grid square.
            {
                //Draws a single square at mouse.
                #region Get Color
                Color objColor = Color.White;
                if (this.selectedBlockType == BlockType.Block) //Block
                    objColor = new Color(255, 0, 0);
                else if (this.selectedBlockType == BlockType.Cliff)   //Cliff
                    objColor = new Color(0, 255, 0);
                else if (this.selectedBlockType == BlockType.Water)   //Water
                    objColor = new Color(0, 0, 255);
                else if (this.selectedBlockType == BlockType.HighGround)   //HighGround
                    objColor = new Color(255, 255, 0);
                #endregion
                spriteBatch.Draw(cellTexture, objPosition, objColor);

                //If a button is held down draw a box of cells.
                #region Draw Box Selection
                if ((lastMouseState.LeftButton == ButtonState.Pressed) || (lastMouseState.RightButton == ButtonState.Pressed))
                {
                    //Convert get presspoint and convert it into map coordiantes.
                    Vector2 boxStart = new Vector2();
                    boxStart.X = ((int)vPressPoint.X) * 15;
                    boxStart.Y = ((int)vPressPoint.Y) * 15;

                    //Convert from screen coordinates to map coordinates.
                    objPosition = objPosition + vCameraPosition;

                    //Find topLeft coordinates, as well as width and height of box.
                    Vector2 boxTopLeft = new Vector2();
                    int boxWidth = 0;
                    int boxHeight = 0;
                    if (boxStart.X < objPosition.X)
                    {
                        boxWidth = (int)(objPosition.X - boxStart.X + 1);
                        boxTopLeft.X = boxStart.X;
                    }
                    else
                    {
                        boxWidth = (int)(boxStart.X - objPosition.X + 1);
                        boxTopLeft.X = objPosition.X;
                    }
                    if (boxStart.Y < objPosition.Y)
                    {
                        boxHeight = (int)(objPosition.Y - boxStart.Y + 1);
                        boxTopLeft.Y = boxStart.Y;
                    }
                    else
                    {
                        boxHeight = (int)(boxStart.Y - objPosition.Y + 1);
                        boxTopLeft.Y = objPosition.Y;
                    }

                    //Draw square at last place pressed.
                    for (int x = 0; x <= boxWidth / 15; x++)
                        for (int y = 0; y <= boxHeight / 15; y++)
                        {
                            Vector2 drawVector = new Vector2(boxTopLeft.X + x * 15, boxTopLeft.Y + y * 15);
                            spriteBatch.Draw(cellTexture, drawVector - vCameraPosition, objColor);
                        }

                }//If: button was held down.
                #endregion

            }//Else draw grid square

            //Draw the mouse pointer.
            spriteBatch.Draw(mouseTexture, new Vector2(this.currentMouseState.X, this.currentMouseState.Y), Color.White);

            #endregion

            spriteBatch.End();
        }

        public void getUserInput(KeyboardState keyboardState, MouseState mouseState, Viewport viewport)
        {
            //If you're not busy with another process
            if (!this.bProcessBusy)
            {
                #region Keyboard Input
                KeyboardState keyboard = Keyboard.GetState();

                #region Move camera
                if (keyboard.IsKeyDown(Keys.Left))
                {
                    this.vCameraPosition.X -= 15;
                }
                if (keyboard.IsKeyDown(Keys.Right))
                {
                    this.vCameraPosition.X += 15;
                }
                if (keyboard.IsKeyDown(Keys.Up))
                {
                    this.vCameraPosition.Y -= 15;
                }
                if (keyboard.IsKeyDown(Keys.Down))
                {
                    this.vCameraPosition.Y += 15;
                }
                #endregion
                #region Load and save
                else if ((keyboard.IsKeyDown(Keys.S)) && (this.lastKeyboardState.IsKeyUp(Keys.S)))
                {
                    this.Save();
                }
                else if ((keyboard.IsKeyDown(Keys.L)) && (this.lastKeyboardState.IsKeyUp(Keys.L)))
                {
                    this.Load();
                }
                #endregion
                #region Change selected object and block type
                else if ((keyboard.IsKeyDown(Keys.Tab)) && (this.lastKeyboardState.IsKeyUp(Keys.Tab)))
                {
                    this.bUseMapObject = (!this.bUseMapObject);
                }
                else if ((keyboard.IsKeyDown(Keys.OemPlus)) && (this.lastKeyboardState.IsKeyUp(Keys.OemPlus)))
                {
                    if (this.bUseMapObject)
                    {
                        this.selectedMapObject++;
                    }
                    else
                    {
                        this.selectedBlockType++;
                    }
                    if (this.bUseMapObject)
                    {
                        if (this.selectedMapObject >= (MapObject.ObjectType)this.nNumberOfObjectTypes)
                        {
                            this.selectedMapObject = MapObject.ObjectType.SpawnPoint;
                        }
                    }
                    else
                    {
                        if (this.selectedBlockType > (BlockType)this.nNumberOfBlockTypes)
                            this.selectedBlockType = BlockType.Block;
                    }
                }
                else if ((keyboard.IsKeyDown(Keys.OemMinus)) && (this.lastKeyboardState.IsKeyUp(Keys.OemMinus)))
                {
                    if (this.bUseMapObject)
                    {
                        this.selectedMapObject--;
                    }
                    else
                    {
                        this.selectedBlockType--;
                    }
                    if (this.bUseMapObject)
                    {
                        if (this.selectedMapObject < 0)
                            this.selectedMapObject = (MapObject.ObjectType)this.nNumberOfObjectTypes - 1;
                    }
                    else
                        if (this.selectedBlockType < (BlockType)1)
                            this.selectedBlockType = BlockType.HighGround;

                }
                #endregion

                lastKeyboardState = keyboard;
                #endregion

                #region MouseInput

                #region Check what has mouse focus
                //If mouse is out of viewport bounds on the right
                if (mouseState.X > viewport.Width)
                {
                    //If within x range of side menu
                    if (mouseState.X < viewport.Width + this.sideMenu.Width)
                    {
                        game.IsMouseVisible = true;
                        this.sideMenu.HasFocus = true;

                        //If within bounds of side menu and left button pressed
                        if (mouseState.LeftButton == ButtonState.Pressed)
                        {
                            //Check to see if any menu items are clicked
                            this.sideMenu.CheckItemClick(new Vector2((mouseState.X - viewport.Width), mouseState.Y));
                        }
                    }
                    else
                    {
                        game.IsMouseVisible = false;
                        this.sideMenu.HasFocus = false;
                    }
                }
                else
                {
                    game.IsMouseVisible = false;
                    this.sideMenu.HasFocus = false;
                }
                #endregion

                if (!this.sideMenu.HasFocus)
                {
                    this.currentMouseState = mouseState;

                    Vector2 mouseMap;
                    mouseMap.X = this.currentMouseState.X + this.vCameraPosition.X;
                    mouseMap.Y = this.currentMouseState.Y + this.vCameraPosition.Y;

                    #region Left Click
                    if ((this.currentMouseState.LeftButton == ButtonState.Pressed) && (lastMouseState.LeftButton == ButtonState.Released))
                    {
                        //Get press point in cell coordinates.
                        this.vPressPoint = mouseMap / 15;

                        if (this.bUseMapObject)
                        {
                            this.vPressPoint.X = (int)(this.vPressPoint.X) * 15;
                            this.vPressPoint.Y = (int)(this.vPressPoint.Y) * 15;

                            this.lstMapObjects.Add(new MapObject(this.selectedMapObject, this.vPressPoint));
                        }
                    }
                    else if ((this.currentMouseState.LeftButton == ButtonState.Released) && (lastMouseState.LeftButton == ButtonState.Pressed))
                    {
                        if (this.bUseMapObject)
                        {
                            lastMouseState = this.currentMouseState;
                            return;
                        }
                        else
                        {

                            //Find release point in cell coordinates.
                            vReleasePoint = mouseMap / 15;

                            //Get square topLeft.
                            Vector2 squareTopLeft;
                            if (this.vPressPoint.X < vReleasePoint.X)
                                squareTopLeft.X = (int)this.vPressPoint.X;
                            else
                                squareTopLeft.X = (int)this.vReleasePoint.X;

                            if (this.vPressPoint.Y < vReleasePoint.Y)
                                squareTopLeft.Y = (int)this.vPressPoint.Y;
                            else
                                squareTopLeft.Y = (int)vReleasePoint.Y;

                            //Get square width and height in cells.
                            int squareWidth = ((int)this.vPressPoint.X - (int)vReleasePoint.X);
                            int squareHeight = ((int)this.vPressPoint.Y - (int)vReleasePoint.Y);
                            if (squareWidth < 0)
                                squareWidth *= (-1);
                            if (squareHeight < 0)
                                squareHeight *= -(1);

                            for (int x = (int)squareTopLeft.X; x <= squareTopLeft.X + squareWidth; x++)
                            {
                                for (int y = (int)squareTopLeft.Y; y <= squareTopLeft.Y + squareHeight; y++)
                                {
                                    if ((x < 0) || (x >= mapCellWidth))
                                        break;
                                    if ((y < 0) || (y >= mapCellHeight))
                                        break;

                                    cell[x, y] = (byte)this.selectedBlockType;
                                }
                            }
                        }
                    }//Else if button is released
                    #endregion

                    #region Right Click
                    if ((this.currentMouseState.RightButton == ButtonState.Pressed) && (lastMouseState.RightButton == ButtonState.Released))
                    {
                        //Get press point in cell coordinates.
                        this.vPressPoint = mouseMap / 15;
                    }
                    else if ((this.currentMouseState.RightButton == ButtonState.Released) && (lastMouseState.RightButton == ButtonState.Pressed))
                    {
                        if (this.bUseMapObject)
                        {
                            lastMouseState = this.currentMouseState;
                            return;
                        }

                        //Find release point in cell coordinates.
                        vReleasePoint = mouseMap / 15;

                        //Get square topLeft.
                        Vector2 squareTopLeft;
                        if (this.vPressPoint.X < vReleasePoint.X)
                            squareTopLeft.X = (int)this.vPressPoint.X;
                        else
                            squareTopLeft.X = (int)vReleasePoint.X;

                        if (this.vPressPoint.Y < vReleasePoint.Y)
                            squareTopLeft.Y = (int)this.vPressPoint.Y;
                        else
                            squareTopLeft.Y = (int)vReleasePoint.Y;

                        //Get square width and height in cells.
                        int squareWidth = ((int)this.vPressPoint.X - (int)vReleasePoint.X);
                        int squareHeight = ((int)this.vPressPoint.Y - (int)vReleasePoint.Y);
                        if (squareWidth < 0)
                            squareWidth *= (-1);
                        if (squareHeight < 0)
                            squareHeight *= -(1);

                        for (int x = (int)squareTopLeft.X; x <= squareTopLeft.X + squareWidth; x++)
                            for (int y = (int)squareTopLeft.Y; y <= squareTopLeft.Y + squareHeight; y++)
                            {
                                if ((x < 0) || (x >= mapCellWidth))
                                    break;
                                if ((y < 0) || (y >= mapCellHeight))
                                    break;

                                cell[x, y] = 0;
                            }
                    }//Else if button is released
                    #endregion
                    lastMouseState = mouseState;
                }
                #endregion
            }
            else
            {
                this.currentMouseState = mouseState;
                lastMouseState = mouseState;
                //Ignore all inputs until no longer busy
            }
        }//Method get user Input

        public String gridToString()
        {
            String str = "\n";

            for (int y = 0; y < mapCellHeight; y++)
            {
                for (int x = 0; x < mapCellWidth; x++)
                {
                    str += cell[x, y].ToString();
                    str += " ";
                }
                str += "\n";
            }
            str += ";";

            return str;
        }
    }
}
